/*
 * Copyright 2011 LMAX Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.sgy.disruptor;

import java.util.concurrent.locks.LockSupport;

/**
 * Sleeping strategy that initially spins, then uses a Thread.yield(), and
 * eventually sleep (<code>LockSupport.parkNanos(n)</code>) for the minimum
 * number of nanos the OS and JVM will allow while the
 * {@link com.lmax.disruptor.EventProcessor}s are waiting on a barrier.
 *
 * <p>This strategy is a good compromise between performance and CPU resource.
 * Latency spikes can occur after quiet periods.  It will also reduce the impact
 * on the producing thread as it will not need signal any conditional variables
 * to wake up the event handling thread.
 */
public final class SleepingWaitStrategy implements WaitStrategy {
    private static final int SPIN_THRESHOLD = 100;
    private static final int DEFAULT_RETRIES = 200;
    private static final long DEFAULT_SLEEP = 100;
    /**
     * 自选次数，默认为200次
     */
    private final int retries;
    /**
     * 睡眠时间
     */
    private final long sleepTimeNs;

    /**
     * Provides a sleeping wait strategy with the default retry and sleep settings
     */
    public SleepingWaitStrategy() {
        this(DEFAULT_RETRIES, DEFAULT_SLEEP);
    }

    /**
     * @param retries How many times the strategy should retry before sleeping
     */
    public SleepingWaitStrategy(final int retries) {
        this(retries, DEFAULT_SLEEP);
    }

    /**
     * @param retries     How many times the strategy should retry before sleeping
     * @param sleepTimeNs How long the strategy should sleep, in nanoseconds
     */
    public SleepingWaitStrategy(final int retries, final long sleepTimeNs) {
        this.retries = retries;
        this.sleepTimeNs = sleepTimeNs;
    }

    @Override
    public long waitFor(
            final long sequence, final Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier)
            throws AlertException {
        long availableSequence;
        // 自选次数
        int counter = retries;
        // 比较生产者和消费者的进度序号
        // 消费者序号大于等于生产者序号
        while ((availableSequence = dependentSequence.get()) < sequence) {
            // 如果需要阻塞则去执行阻塞等
            counter = applyWaitMethod(barrier, counter);
        }
        return availableSequence;
    }

    @Override
    public void signalAllWhenBlocking() {
    }

    private int applyWaitMethod(final SequenceBarrier barrier, final int counter)
            throws AlertException {
        barrier.checkAlert();
        // 当前自旋大于100小于200
        if (counter > SPIN_THRESHOLD) {
            // counter-1后返回
            return counter - 1;
        } else if (counter > 0) {
            // 走到这里说明counter大于0小于等于100
            // 也就是多次尝试自选还是不能继续向下工作，则尝试让该线程让出CPU
            Thread.yield();
            // counter-1后返回
            return counter - 1;
        } else {
            // 走到这里说明自旋次数已经达到200了，使用LockSupport让线程短暂的阻塞一会
            LockSupport.parkNanos(sleepTimeNs);
        }
        return counter;
    }
}
